/* *****************************************************************************************************************
	http://www.w3.org/TR/css3-selectors/
	http://dean.edwards.name/my/cssQuery/
	http://www.openjs.com/scripts/dom/css_selector/
	http://www.456bereastreet.com/archive/200601/css_3_selectors_explained/
	
	http://www.w3.org/TR/css3-selectors/#structural-pseudos

	http://ejohn.org/blog/qualified-selectors-in-jquery/
	
	this.scope.querySelectorAll ? this.scope.querySelectorAll(rule) : 	
	
	http://pivots.pivotallabs.com/users/alex/blog/articles/427-xpath-css-class-matching
	http://plasmasturm.org/log/444/
	
***************************************************************************************************************** */

compono.HTMLElements.Element.getChildren = function(formula){
	if(this.children&&!formula){
		return this.children;
	};
	this.children=[], arg=[], s=/(?:(\w+)n)(([+-])(\d+))?/;
	if(formula){
		for(var i=0, l=this.childNodes.length; i<l; i++){
			if(this.childNodes[i].nodeType==1 && this.childNodes[i].nodeName==formula){
				arg[arg.length] = this.childNodes[i];
			}
		}
	}else{
		for(var i=0, l=this.childNodes.length; i<l; i++){
			if(this.childNodes[i].nodeType==1){
				arg[arg.length] = this.childNodes[i];
			}
		}
	};
	if(this.children&&!formula){
		return this.children=arg;
	};
	return arg;
}



compono.Selector = compono.Class({
	spaces:/^\s+/,
	comma:/\s*,\s*/,
	html:false,
	constructor:function(rule,scope,test){
		this.html = this.core.HTMLElements.Element;
		if(rule){
			return this.query(rule,scope,test)
		};
		return this
	},
	query:function(rule,scope,test){
		var i, r, l, prop, selector, $function, result=[], finalResult=[], baserule;

		if(!rule){
			return [];
		}
		
		//testing the rule comma
		if(this.comma.test(rule)){
			//using the function itself
			for(var i=0,z=rule.split(this.comma);i<z.length;i++){
				finalResult = finalResult.concat(arguments.callee.call(this,z[i],scope))
			};
			return finalResult;
		};
		
		baserule = rule;
		scope = [scope||document];
		
		test = test || false;
		
		//labeling the while to force the if clause break the for...in loop
		//testing if the rule has text in it
		START:
		while(rule.length){
			
			//navigating in the syntax to recognize what structure is
			for(prop in this.syntax){
				
				//testing if the prop from syntax match with some info in the prototype
				if(prop in this){
					
					selector = this.syntax[prop].exec(rule);
						
					if(selector){
		
						//cleaning the current selector from the entire selector
						rule = rule.substr(selector[0].length).replace(this.spaces,"");

						//it can be used by the rule itself and/or with some sub-function
						//_this["#"]
						//_this[":"]["first-child"]
						$function = this[ prop ];

						if(typeof($function)=="object"){
							$function = selector[1] in $function ? $function[ selector[1] ] : $function[ prop ]
						};
						
						if($function){
							
							selector[selector.length] = test;
						
							for(i=0,l=scope.length;i<l;i++){
								
								//fixing the first part of the selector with the current scope;
								selector[0] = scope[i];
								
								//running the $function with the selector as args
								//first selector args is the scope;
								r = $function.apply(this,selector);
								
								if(r){
								
									//$function can return any kind of info (boolean, Array or HTMLCollection, Node)
									if(typeof(r)=="boolean"&&r==true){

										//restarting the 'r', because it's a boolean and it needs to be a node;
										r = scope[i];
										result[result.length] = scope[i]

									}else if(r instanceof Array || (r.length&&typeof(r)!="string")){
									
										result = r.concat(result)
										
									}else if(r.nodeType){
										
										result[result.length] = r
										
									};
									
									//if it's in the last rule it means that we achived our target
									if(rule.length==0){
										finalResult = finalResult.concat(r)
									}
								}
							}
						};
						
						//restarting the scope based on the result;
						scope = result;
						
						//restarting the result 
						result = [];
					
						//if some syntax match with the rule the for...in loop should be canceled
						continue START
					}
				}
			};
			
			//if the for...in loop didn't change the rule, it means there's no match
			if(baserule === rule){
				finalResult = []
			}
		};
		return test ? finalResult.length==0 : finalResult
	},
	syntax:{
		"#":/^#([\w-_]+)/,
		"*":/^([*\w-_]+)/,
		".":/^\.([\w-_]+)/,
		":":/^:([\w-_]+)(?:\(([^)]+)?\))?/,
		"+":/^([+~<>])\s+(\w+)/,
		"[]":{
			re:/^(?:\[([\w-_]+)(?:([~$*|=^]{1,2})['"]?([.:/\w-_\s#]+)["']?)?\])/,
			exec:function(str){
				var x = this.re.exec(str);
				return x ? [x[0],x[2],x[1],x[3]] : null;
			}
		}
	},
	"*":function(scope,tag,test){
		if(test==true){
			return scope.nodeName.toLowerCase() == tag.toLowerCase();
		};
		return compono.$A(scope.getElementsByTagName(tag))
	},
	"#":function(scope,id,test){	
		return (test==true || scope!==document) ? this["[]"]["="](scope,"=","id",id) : document.getElementById(id);
	},
	".":function(scope,classname){	
		if(scope === document){
			var a = arguments.callee;
			return this["*"](scope,"*").filter(function(o){
						return a.call(this,o,classname)
					},this)
		};
		return this.html.hasClassName.call(scope,classname)
	},
	"+":{
		">":function(scope,op,next){
			return (next!="*" ? this.html.firstOfType : this.html.first).call(scope,next) || false
		},
		"<":function(scope,op,next){
			if(next!="*"){
				var o = this.html.parent.call(scope)
				return o.nodeName.toLowerCase()==next.toLowerCase() ? o : false;
			}else{
				return this.html.parent.call(scope);
			};
			return false
		},
		"~":function(scope,op,next){
			return this.html.nextOfType.call(scope,next) || false
		},
		"-":function(scope,op,previous){
			var n = this.html.previous.call(scope);
			return (n.nodeType && (previous == "*" || previous == n.nodeName.toLowerCase())) ? n : false
		},
		"+":function(scope,op,next){
			var n = this.html.next.call(scope);
			return (n.nodeType && (next == "*" || next == n.nodeName.toLowerCase())) ? n : false
		}
	},
	"[]":{
		"[]":function(scope,op,attr,value){		return scope.getAttribute(attr) != null},
		"^=":function(scope,op,attr,value){		return scope.getAttribute(attr).indexOf(value) == 0 },
		"*=":function(scope,op,attr,value){		return scope.getAttribute(attr).indexOf(value) > -1 },
		"~=":function(scope,op,attr,value){		return compono.hash.selector.get(value).test(scope.getAttribute(attr)) },
		"|=":function(scope,op,attr,value){		return compono.hash.selector.get(value).test(scope.getAttribute(attr)) },
		"=":function(scope,op,attr,value){		return scope.getAttribute(attr) == value},
		"$=":function(scope,op,attr,value){		
			var v = scope.getAttribute(attr);
			return v.substring(v.length-value.length,v.length) == value
		}
	},
	":":{
		"target":function(scope,name){			return window.location.hash&&(window.location.hash.substr(1)==scope.getAttribute("id")) },
		"elements":function(scope,name){		return this.$T("input,textarea,select,button",scope);},
		"checkbox":function(scope,name){		return this["[]"]["="](scope,"type",name)},
		"radio":function(scope,name){			return this["[]"]["="](scope,"type",name)},
		"file":function(scope,name){			return this["[]"]["="](scope,"type",name)},
		"text":function(scope,name){			return this["[]"]["="](scope,"type",name)},
		"button":function(scope,name){			return this["[]"]["="](scope,"type",name)},
		"image":function(scope,name){			return this["[]"]["="](scope,"type",name)},
		"submit":function(scope,name){			return this["[]"]["="](scope,"type",name)},
		"hidden":function(scope,name){			return this["[]"]["="](scope,"type",name)},
		"password":function(scope,name){		return this["[]"]["="](scope,"type",name)},
		"reset":function(scope,name){			return this["[]"]["="](scope,"type",name)},
		"root":function(scope,name){			return document.documentElement||document.body.parentNode},
		"checked":function(scope,name){			return scope.checked},
		"enabled":function(scope,name){			return !(scope.disabled)},
		"disabled":function(scope,name){		return scope.disabled},
		"empty":function(scope,name){			return scope.childNodes.length>0 ? scope.getElementsByTagName("*").length==0 : false;},
		"contains":function(scope,name,value){	return scope.innerHTML.indexOf(value)>-1		},
		"lang":function(scope,value){			return this["[]"]["|="](scope,"lang",value)		},
		"first-child":function(scope,name){		return this.html.first.call(scope.parentNode) === scope},
		"first-of-type":function(scope,name){	return this.html.firstOfType.call(scope.parentNode) === scope;},
		"last-child":function(scope,name){		return this.html.last.call(scope.parentNode) === scope;},
		"last-of-type":function(scope,name){	return this.html.lastOfType.call(scope.parentNode) === scope;},
		"only-child":function(scope,name){		return this.html.getChildren.call(scope.parentNode).length==1;},
		"only-of-type":function(scope,name){	return this.html.getChildren.call(scope.parentNode).length==1;},
		"not":function(scope,name,value){		return this.query(value,scope,true);},
		"nth-child":function(scope,value,formula){
			var r = /(?:(\w+)n)(([+-])(\d+))?/;
			if(formula&&r.test(formula)){
				console.log(1,r.exec(formula),scope)
				return this.html.filter.call(scope.parentNode,value);
			};
			return false;
		},	
		"nth-last-child":function(scope,value){},
		"nth-of-type":function(scope,value){},
		"nth-last-of-type":function(scope,value){}
	},
	"::":function(scope,event){
		//scope.event(event);
		return scope;
	}
});


